package bit.lotterysystem.service.mq;


import bit.lotterysystem.common.exception.ServiceException;
import bit.lotterysystem.common.utils.JacksonUtil;
import bit.lotterysystem.common.utils.MailUtil;
import bit.lotterysystem.common.utils.SMSUtil;
import bit.lotterysystem.controller.param.DrawPrizeParam;
import bit.lotterysystem.dao.dataobject.ActivityPrizeDO;
import bit.lotterysystem.dao.dataobject.WinningRecordDO;
import bit.lotterysystem.dao.mapper.ActivityPrizeMapper;
import bit.lotterysystem.dao.mapper.WinningRecordMapper;
import bit.lotterysystem.service.DrawPrizeService;
import bit.lotterysystem.service.activityStatus.ActivityStatusManager;
import bit.lotterysystem.service.dto.ConvertActivityStatusDTO;
import bit.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import bit.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import bit.lotterysystem.service.enums.ActivityStatusEnum;
import bit.lotterysystem.service.enums.ActivityUserStatusEnum;
import cn.hutool.core.date.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

import static bit.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {

    private static final Logger logger= LoggerFactory.getLogger(MqReceiver.class);

    @Autowired
    private DrawPrizeService drawPrizeService;

    @Autowired
    private ActivityStatusManager activityStatusManager;


    @Qualifier("asyncServiceExecutor")
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private MailUtil mailUtil;

    @Autowired
    private SMSUtil smsUtil;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;

    @RabbitHandler
    public void process(Map<String,String> message) throws Exception {
        //成功接收到队列里的消息
        logger.info("Mq成功接收到消息，message:{}", JacksonUtil.writeValueAsString(message));
        String paramString= message.get("messageData");
        DrawPrizeParam param= JacksonUtil.readValue(paramString, DrawPrizeParam.class);
        //处理抽奖的流程
        try{

            //校验抽奖请求是否有效
            drawPrizeService.checkDrawPrizeParam(param);
            //状态扭转处理
            statusConvert(param);

            //保存中奖者名单
            List<WinningRecordDO> winningRecordDOList=
                    drawPrizeService.saveWinnerRecords(param);


            if(true){
                return;
            }

            //通知中奖者（邮箱、短信）
            syncExecute(winningRecordDOList);

        }catch(ServiceException e){
            logger.error("处理MQ消息异常！{}：{}",e.getCode(),e.getMessage(),e);
            //如果发生异常，需要保证事务一致性（回滚），抛出异常
            rollback(param);

            //抛出异常：消息重试（解决异常：代码bug\网络问题、服务问题）
            throw e;
        }catch(Exception e){
            logger.error("处理MQ消息异常!",e);
            rollback(param);
            //如果发生异常，需要保证事务一致性（回滚），抛出异常
            throw e;
           // throw new ServiceException(); // 转换为自定义异常抛出
        }




    }

    /**
     * 处理抽奖异常的回滚行为
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
        //1、回滚状态：活动、奖品、人员
        //状态是否需要回滚
        if(!statusNeedRollback(param)){
            //不需要:return;
            return;
        }


        //需要回滚：回滚
        rollbackStatus(param);

        //2、回滚中奖者名单
        //是否需要回滚
        if(!winnerNeedRollback(param)){
            //不需要：return
            return;
        }
        //需要:回滚
        rollbackWinner(param);
    }

    /**
     * 回滚中奖记录：删除奖品上的中奖者
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        //判断活动中的奖品是否存在中奖者
        int count=winningRecordMapper.countByAPId(param.getActivityId(),param.getPrizeId());
        return count>0;
    }

    private void rollbackStatus(DrawPrizeParam param) {
        //涉及状态的恢复，使用ActivityStatusManager
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList()
                        .stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);


        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    private boolean statusNeedRollback(DrawPrizeParam param) {
        //判断活动、奖品、人员表是否已经扭转（正常思路）
        //扭转状态时，保证了事务一致性，要么都扭转了，要么都没扭转（不包含活动）
        //因此，只用判断人员/奖品是否扭转过，就能判断出状态是否全部扭转
        //结论：判断奖品状态是否扭转，就能判断出全部状态是否扭转
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());
        if(activityPrizeDO.getStatus()
                .equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name())){
            return true;
        }
        return false;

    }

    /**
     * 并发处理抽奖后续流程
     * @param winningRecordDOList
     */
    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        //通过线程池threadPoolTaskExecutor
        //扩展：加入策略模式或者其他设计模式来完成后续的异步操作
        //短信通知
        threadPoolTaskExecutor.execute(()->sendMessage(winningRecordDOList));
        //邮件通知
        threadPoolTaskExecutor.execute(()->sendMail(winningRecordDOList));

    }

    /**
     * 发邮件
     * @param winningRecordDOList
     */
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)){
            logger.info("中奖列表为空，不用发邮件");
            return;
        }
        for(WinningRecordDO winningRecordDO:winningRecordDOList){

            String context="Hi,"+winningRecordDO.getWinnerName()+"。"+
                    "恭喜你在"+winningRecordDO.getActivityName()+"活动中获得"+
                    ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()+
                    ",奖品为"+winningRecordDO.getPrizeName()+
                    "。获奖时间为"+DateUtil.formatTime(winningRecordDO.getWinningTime())+",请尽快领取您的奖励！";

            System.out.println("测试未格式化时间"+winningRecordDO.getWinningTime());
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),
                    "中奖通知",
                    context
                    );

        }
    }

    /**
     * 发短信
     * @param winningRecordDOList
     */
    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)){
            logger.info("中奖列表为空，不用发邮件");
            return;
        }
        for(WinningRecordDO winningRecordDO:winningRecordDOList){

            Map<String,String>map=new HashMap<>();
            map.put("name",winningRecordDO.getWinnerName());
            map.put("activityName",winningRecordDO.getActivityName());
            map.put("prizeTier",ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage());
            map.put("prizeName",winningRecordDO.getPrizeName());
            map.put("winningTime",DateUtil.formatTime(winningRecordDO.getWinningTime()));
            smsUtil.sendMessage("SMS_318910945",
                    winningRecordDO.getWinnerPhoneNumber().getValue(),
                    JacksonUtil.writeValueAsString(map)
                    );
        }

    }


    /**
     * 状态扭转
     * @param param
     */
    private void statusConvert(DrawPrizeParam param){
        ConvertActivityStatusDTO convertActivityStatusDTO=new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList()
                        .stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList()));
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);
        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }

//    /**
//     * 状态扭转(没有用设计模式)
//     * @param param
//     */
//    public void statusConvert(DrawPrizeParam param){
//        //问题：
//        //1.活动状态扭转有依赖性，导致代码维护性差
//        //2.状态扭转条件可能会扩展，当前写法，扩展性差，维护性差
//        //3.代码的灵活性、扩展性、维护性极差
//        //解决方法：设计模式（责任链设计模式、策略模式）
//        //活动:RUNNING->COMPLETED?全部奖品抽完才改变状态
//        //奖品:INIT->COMPLETED
//        //人员列表:INIT->COMPLETED
//
//        //1 扭转奖品状态
//        //查询活动关联的奖品信息
//        //判断当前状态是否不是COMPLETED,若不是，要扭转
//
//        //2 扭转人员状态
//        //查询活动关联的人员信息
//        //判断当前状态是否不是COMPLETED,若不是，要扭转
//
//        //3 扭转活动状态（必须在扭转奖品状态之后完成）
//        //查询活动信息
//        //判断当前状态是否不是COMPLETED,若不是，且全部奖品抽完后，才要扭转
//
//        //更新活动完整信息的缓存
//    }

}
